using System;
using System.Collections;
using System.Reflection;

namespace NUtil.beans {
    public class GraphStructureBrowser {
        public GraphStructureBrowser(NodeVisitor nodeVisitor, bool visitValueObjets) {
            _nodeVisitor = nodeVisitor;
            _visitValueObjets = visitValueObjets;
            _visitedObjects = new ArrayList();
        }

        public delegate void NodeVisitor(string name, Type type, int level);

        public void Browse(string name, Type typeToBrowsed, int level) {
            if (mayNotHaveAnyChilden(typeToBrowsed)) {
                throw new Exception(MethodBase.GetCurrentMethod().Name + " can not be called with value types");
            }
            typeToBrowsed = getFirstImplementationIfInterface(typeToBrowsed);
            _nodeVisitor(name, typeToBrowsed, level);
            if (!_visitedObjects.Contains(typeToBrowsed)) {
                _visitedObjects.Add(typeToBrowsed);
                browseChildren(typeToBrowsed, level + 1);
            }
        }

        private void browseChildren(Type typeToClone, int level) {
            FieldInfo[] fieldInfos = getAllInstanceFields(typeToClone);
            foreach (FieldInfo fieldInfo in fieldInfos) {
                if (mayNotHaveAnyChilden(fieldInfo.FieldType)) {
                    if (_visitValueObjets) {
                        _nodeVisitor(fieldInfo.Name, fieldInfo.FieldType, level + 1);
                    }
                }
                else if (fieldInfo.FieldType.Equals(typeof (IList))) {
                    _nodeVisitor(fieldInfo.Name, fieldInfo.FieldType, level + 1);
                }
                else {
                    Browse(fieldInfo.Name, fieldInfo.FieldType, level + 1);
                }
            }
        }

        private static Type getFirstImplementationIfInterface(Type typeToClone) {
            if (typeToClone.IsInterface) {
                string interfaceName = typeToClone.FullName;
                Type possibleImpl = typeToClone.Assembly.GetType(interfaceName + "Impl");
                if (possibleImpl != null) {
                    typeToClone = possibleImpl;
                }
            }
            return typeToClone;
        }


        private FieldInfo[] getAllInstanceFields(Type objectToClone) {
            return
                objectToClone.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
        }

        private bool mayNotHaveAnyChilden(Type type) {
            return type.IsValueType || typeof (String).Equals(type) || typeof (Guid).Equals(type);
        }

        private ArrayList _visitedObjects;
        private NodeVisitor _nodeVisitor;
        private readonly bool _visitValueObjets;
    }
}